﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using EnvSafe.Expression;

namespace Heroius.DBTransitor
{
    internal class MappingField
    {
        internal MappingField(string Name, SqlDbType SQLType, bool Nullable, int MappingIndex)
        {
            this.Name = Name;
            this.SQLType = SQLType;
            this.Nullable = Nullable;
            this.MappingIndex = MappingIndex;
            MappingRule = null;
        }

        internal string Name { get; private set; }

        internal SqlDbType SQLType { get; private set; }

        internal int MappingIndex { get; private set; }

        internal bool Nullable { get; private set; }

        internal bool HasMapping { get { return MappingIndex != -1; } }

        internal bool HasRule { get { return MappingRule != null; } }

        internal void DefineRule(
            string nos, string nus, string cs, string rs,
            Procedure NoMapping, Procedure MappingNull, Procedure Condition, Procedure ResultExpression)
        {
            MappingRule = new FieldRule()
            {
                _RelativeField = this,
                nos = nos,
                nus = nus,
                cs = cs,
                rs = rs,
                no = NoMapping,
                nu = MappingNull,
                c = Condition,
                r = ResultExpression
            };
        }

        internal void RemoveRule()
        {
            if (MappingRule != null)
            {
                MappingRule = null;
            }
        }

        /// <summary>
        /// these rules are actually a collection of single rules
        /// </summary>
        internal FieldRule MappingRule;

        /// <summary>
        /// get the mapping value, which will be modified by rules if there's any
        /// </summary>
        /// <returns></returns>
        internal object GetRuledValue(SqlDataReader RowParams)
        {
            //with mapping, use mapping rules first
            if (HasRule)
                return MappingRule.GetRuledValue(RowParams);
            else//no rule
                return SqlDataConverter.ChangeType(
                                (HasMapping) ? RowParams[MappingIndex] : null,
                                SQLType, Nullable);
        }
    }

    //MappingField : FieldRule = 0 : 1
    /// <summary>
    /// field rule does not support 'jumpover', instead it always returns a value - event it's null
    /// </summary>
    internal class FieldRule
    {
        internal FieldRule() { }
        internal MappingField _RelativeField;
        internal Procedure no, nu, c, r;
        internal string nos, nus, cs, rs;

        internal object GetRuledValue(SqlDataReader RowParams)
        {
            Dictionary<string, string> p = new Dictionary<string, string>();
            for (int i = 0; i < RowParams.FieldCount; i++)
            {
                p.Add(
                Step3Helper.EncodeUserVar(RowParams.GetName(i)),
                SqlDataConverter.ChangeType(RowParams[i], RowParams.GetDataTypeName(i), false).ToString()
                );
            }
            if (_RelativeField.HasMapping)
            {
                if (nu != null && RowParams[_RelativeField.MappingIndex] == null)
                    return nu.Calculate(p);
                if (c != null && c.Calculate(p) == "1")
                    return r.Calculate(p);
                //no rule matches
                return SqlDataConverter.ChangeType(
                                (_RelativeField.HasMapping) ? RowParams[_RelativeField.MappingIndex] : null,
                                _RelativeField.SQLType, _RelativeField.Nullable);
            }
            else
            {
                return no.Calculate(p);
            }
            throw new Exception("empty rule is useless");
        }
    }

    /// <summary>
    /// there should be only one instance in this application
    /// </summary>
    internal class RecordRule
    {
        internal RecordRule(bool ExecOnSatisfied, Procedure LogicExpression)
        {
            _ExecOnSatisfied = ExecOnSatisfied;
            l = LogicExpression;
        }

        bool _ExecOnSatisfied;
        Procedure l;

        /// <summary>
        /// check row data to decide (with ExecOnsatisfied property) if the row should be validated for mapping
        /// </summary>
        /// <param name="RowParams"></param>
        /// <returns></returns>
        internal bool Validate(SqlDataReader RowParams)
        {
            bool Satisfied = false; //use logic expression
            Dictionary<string, string> p = new Dictionary<string, string>();
            for (int i = 0; i < RowParams.FieldCount; i++)
            {
                p.Add(
                Step3Helper.EncodeUserVar(RowParams.GetName(i)),
                SqlDataConverter.ChangeType(RowParams[i], RowParams.GetDataTypeName(i), true).ToString()
                );
            }
            Satisfied = l.Calculate(p) == "1";
            return Satisfied ^ _ExecOnSatisfied;
        }
    }
}
